﻿// Copyright (c) 2014 Morten Bakkedal
// This code is published under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;

using FuncLib.DualFunctions.Compilation;
using FuncLib.DualFunctions.Expressions;

namespace FuncLib.DualFunctions
{
	public partial class DualFunction
	{
		/*[DebuggerStepThrough]
		public static implicit operator DualFunction(double a)
		{
			return DualConstantFunction.Create(a);
		}*/

		[DebuggerStepThrough]
		public static DualFunction operator +(DualFunction f1, DualFunction f2)
		{
			return new AddFunction(f1, f2);
		}

		[DebuggerStepThrough]
		public static DualFunction operator +(DualFunction f, double a)
		{
			if (a == 0.0)
			{
				return f;
			}

			return new AddConstantFunction(f, a);
		}

		[DebuggerStepThrough]
		public static DualFunction operator +(double a, DualFunction f)
		{
			return f + a;
		}

		[DebuggerStepThrough]
		public static DualFunction operator -(DualFunction f)
		{
			return new UnaryMinusFunction(f);
		}

		[DebuggerStepThrough]
		public static DualFunction operator -(DualFunction f1, DualFunction f2)
		{
			return new SubtractFunction(f1, f2);
		}

		[DebuggerStepThrough]
		public static DualFunction operator -(DualFunction f, double a)
		{
			if (a == 0.0)
			{
				return f;
			}

			return f + (-a);
		}

		[DebuggerStepThrough]
		public static DualFunction operator -(double a, DualFunction f)
		{
			if (a == 0.0)
			{
				return -f;
			}

			return new SubtractConstantFunction(f, a);
		}

		[DebuggerStepThrough]
		public static DualFunction operator *(DualFunction f1, DualFunction f2)
		{
			return new MultiplyFunction(f1, f2);
		}

		[DebuggerStepThrough]
		public static DualFunction operator *(DualFunction f, double a)
		{
			if (a == 1.0)
			{
				return f;
			}

			if (a == -1.0)
			{
				return -f;
			}

			return new MultiplyConstantFunction(f, a);
		}

		[DebuggerStepThrough]
		public static DualFunction operator *(double a, DualFunction f)
		{
			return f * a;
		}

		[DebuggerStepThrough]
		public static DualFunction operator /(DualFunction f1, DualFunction f2)
		{
			return new DivideFunction(f1, f2);
		}

		[DebuggerStepThrough]
		public static DualFunction operator /(DualFunction f, double a)
		{
			return f * (1.0 / a);
		}

		[DebuggerStepThrough]
		public static DualFunction operator /(double a, DualFunction f)
		{
			return new DivideConstantFunction(f, a);
		}

		[Serializable]
		private class AddFunction : DualFunction
		{
			private DualFunction f1, f2;

			public AddFunction(DualFunction f1, DualFunction f2)
			{
				this.f1 = f1;
				this.f2 = f2;

				TestFunction = f1.TestFunction + f2.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc1 = generator.GetCompiledFunction(f1);
				GeneratedFunction fc2 = generator.GetCompiledFunction(f2);

				Expression g = fc1.Expression + fc2.Expression;
				Expression g1 = 1.0;
				Expression g2 = 1.0;

				return generator.AddBinaryFunction(this, fc1, fc2, g, g1, g2, 0.0, 0.0, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f1;
				yield return f2;
			}
		}

		[Serializable]
		private class AddConstantFunction : DualFunction
		{
			private DualFunction f;
			private double a;

			public AddConstantFunction(DualFunction f, double a)
			{
				this.f = f;
				this.a = a;

				TestFunction = f.TestFunction + a;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc = generator.GetCompiledFunction(f);

				Expression g = fc.Expression + a;
				Expression g1 = 1.0;

				return generator.AddUnaryFunction(this, fc, g, g1, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f;
			}
		}

		[Serializable]
		private class SubtractFunction : DualFunction
		{
			private DualFunction f1, f2;

			public SubtractFunction(DualFunction f1, DualFunction f2)
			{
				this.f1 = f1;
				this.f2 = f2;

				TestFunction = f1.TestFunction - f2.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc1 = generator.GetCompiledFunction(f1);
				GeneratedFunction fc2 = generator.GetCompiledFunction(f2);

				Expression g = fc1.Expression - fc2.Expression;
				Expression g1 = 1.0;
				Expression g2 = -1.0;

				return generator.AddBinaryFunction(this, fc1, fc2, g, g1, g2, 0.0, 0.0, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f1;
				yield return f2;
			}
		}

		[Serializable]
		private class SubtractConstantFunction : DualFunction
		{
			private DualFunction f;
			private double a;

			public SubtractConstantFunction(DualFunction f, double a)
			{
				this.f = f;
				this.a = a;

				TestFunction = a - f.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc = generator.GetCompiledFunction(f);

				Expression g = a - fc.Expression;
				Expression g1 = -1.0;

				return generator.AddUnaryFunction(this, fc, g, g1, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f;
			}
		}

		[Serializable]
		private class UnaryMinusFunction : DualFunction
		{
			private DualFunction f;

			public UnaryMinusFunction(DualFunction f)
			{
				this.f = f;

				TestFunction = -f.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc = generator.GetCompiledFunction(f);

				Expression g = -fc.Expression;
				Expression g1 = -1.0;

				return generator.AddUnaryFunction(this, fc, g, g1, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f;
			}
		}

		[Serializable]
		private class MultiplyFunction : DualFunction
		{
			private DualFunction f1, f2;

			public MultiplyFunction(DualFunction f1, DualFunction f2)
			{
				this.f1 = f1;
				this.f2 = f2;

				TestFunction = f1.TestFunction * f2.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc1 = generator.GetCompiledFunction(f1);
				GeneratedFunction fc2 = generator.GetCompiledFunction(f2);

				Expression fe1 = Expression.MarkLocal(fc1.Expression);
				Expression fe2 = Expression.MarkLocal(fc2.Expression);
				Expression g = fe1 * fe2;
				Expression g1 = fe2;
				Expression g2 = fe1;
				Expression g12 = 1.0;

				return generator.AddBinaryFunction(this, fc1, fc2, g, g1, g2, 0.0, g12, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f1;
				yield return f2;
			}
		}

		[Serializable]
		private class MultiplyConstantFunction : DualFunction
		{
			private DualFunction f;
			private double a;

			public MultiplyConstantFunction(DualFunction f, double a)
			{
				this.f = f;
				this.a = a;

				TestFunction = f.TestFunction * a;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc = generator.GetCompiledFunction(f);

				Expression g = a * fc.Expression;
				Expression g1 = a;

				return generator.AddUnaryFunction(this, fc, g, g1, 0.0);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f;
			}
		}

		[Serializable]
		private class DivideFunction : DualFunction
		{
			private DualFunction f1, f2;

			public DivideFunction(DualFunction f1, DualFunction f2)
			{
				this.f1 = f1;
				this.f2 = f2;

				TestFunction = f1.TestFunction / f2.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc1 = generator.GetCompiledFunction(f1);
				GeneratedFunction fc2 = generator.GetCompiledFunction(f2);

				Expression fe1 = Expression.MarkLocal(fc1.Expression);
				Expression fe2 = Expression.MarkLocal(fc2.Expression);
				Expression g = Expression.MarkLocal(fe1 / fe2);
				Expression g1 = Expression.MarkLocal(1.0 / fe2);
				Expression g2 = Expression.MarkLocal(-g / fe2);
				Expression g12 = -g1 / fe2;
				Expression g22 = -2.0 * g2 / fe2;

				return generator.AddBinaryFunction(this, fc1, fc2, g, g1, g2, 0.0, g12, g22);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f1;
				yield return f2;
			}
		}

		[Serializable]
		private class DivideConstantFunction : DualFunction
		{
			private DualFunction f;
			private double a;

			public DivideConstantFunction(DualFunction f, double a)
			{
				this.f = f;
				this.a = a;

				TestFunction = a / f.TestFunction;
			}

			internal override GeneratedFunction Compile(DualCodeGenerator generator)
			{
				GeneratedFunction fc = generator.GetCompiledFunction(f);

				Expression fe = Expression.MarkLocal(fc.Expression);
				Expression g = Expression.MarkLocal(a / fe);
				Expression g1 = Expression.MarkLocal(-g / fe);
				Expression g11 = Expression.MarkLocal(-2.0 * g1 / fe);

				return generator.AddUnaryFunction(this, fc, g, g1, g11);
			}

			internal override IEnumerable<DualFunction> GetDependencies()
			{
				yield return f;
			}
		}
	}
}
